package com.ts.api.module.channel.service.impl;

import com.auth0.jwt.JWT;
import com.gitee.apanlh.util.algorithm.encrypt.symmetric.AES;
import com.gitee.apanlh.util.algorithm.signature.JwtObject;
import com.gitee.apanlh.util.algorithm.signature.JwtUtils;
import com.gitee.apanlh.util.dataformat.JsonUtils;
import com.gitee.apanlh.util.encode.Base64Utils;
import com.gitee.apanlh.util.encode.HexUtils;
import com.gitee.apanlh.util.log.Log;
import com.gitee.apanlh.util.reflection.ClassConvertUtils;
import com.gitee.apanlh.util.valid.Assert;
import com.gitee.apanlh.util.valid.ValidParam;
import com.ts.api.common.constant.BizEnum;
import com.ts.api.common.context.ChannelRequestContext;
import com.ts.api.common.redis.UserRedisKey;
import com.ts.api.config.TokenConfig;
import com.ts.api.exp.BizException;
import com.ts.api.module.channel.dao.ChannelUserDao;
import com.ts.api.module.channel.entity.cache.ChannelCacheUser;
import com.ts.api.module.channel.entity.po.ChannelUserRegisterPO;
import com.ts.api.module.channel.entity.qo.ChannelUserQO;
import com.ts.api.module.channel.service.ChannelLoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ChannelLoginServiceImpl implements ChannelLoginService {

    @Autowired
    private ChannelUserDao channelUserDao;
    @Autowired
    private TokenConfig tokenConfig;

    @Override
    public boolean isLogin() {
        String token = ChannelRequestContext.get().getToken();
        if (ValidParam.isEmpty(token)) {
            return false;
        }
        //  获取载体
        String phone;
        try {
            phone = this.getPayloadPhone(token);
        } catch (Exception e) {
            return false;
        }

        //  解密
        JwtObject jwtObject = JwtUtils.verifyJwt(
            token,
            phone,
            tokenConfig.getAlgorithm()
        );

        if (JwtUtils.isExpire(jwtObject) || !jwtObject.isVerifyFlag()) {
            return false;
        }

        //  获取缓存
        try {
            String decryptPhone = decryptPayloadPhone(phone);
            ChannelCacheUser cache = UserRedisKey.LOGIN_TOKEN.get(decryptPhone, ChannelCacheUser.class);
            return ValidParam.isNotNull(cache);
        } catch (Exception e) {
            Log.get().error("{}", e.getMessage(), e);
            return false;
        }
    }

    @Override
    public ChannelCacheUser getLoginUser() {
        String token = ChannelRequestContext.get().getToken();
        Assert.isNotEmpty(token, () -> {throw new BizException(BizEnum.USER_TOKEN_EMPTY);});

        //  获取载体
        String phone = this.getPayloadPhone(token);
        //  解密
        JwtObject jwtObject = JwtUtils.verifyJwt(
            token,
            phone,
            tokenConfig.getAlgorithm()
        );

        Assert.isFalseThrows(JwtUtils.isExpire(jwtObject), new BizException(BizEnum.USER_TOKEN_EXPIRE));
        Assert.isTrueThrows(jwtObject.isVerifyFlag(), new BizException(BizEnum.USER_TOKEN_ERROR));

        //  获取缓存
        String decryptPhone = decryptPayloadPhone(phone);
        ChannelCacheUser cache = UserRedisKey.LOGIN_TOKEN.get(decryptPhone, ChannelCacheUser.class);
        Assert.isNotNullThrows(cache, new BizException(BizEnum.USER_LOGIN_EMPTY));
        return cache;
    }

    @Override
    public String createToken(ChannelCacheUser channelCacheUser) {
        String phone = channelCacheUser.getPhone();

        ChannelCacheUser cacheUser = UserRedisKey.LOGIN_TOKEN.get(phone, ChannelCacheUser.class);
        if (ValidParam.isNotNull(cacheUser)) {
            return cacheUser.getToken();
        }

        AES cbc = AES.createCbc(HexUtils.decode(tokenConfig.getParamKey()), HexUtils.decode(tokenConfig.getParamIv()));

        String token = JwtUtils.sign(
            cbc.encryptToHex(phone),
            tokenConfig.getAlgorithm(),
            tokenConfig.getExpireTimeToMillis()
        );

        channelCacheUser.setToken(token);
        UserRedisKey.LOGIN_TOKEN.setForSeconds(phone, channelCacheUser, tokenConfig.getExpireTimeToSeconds());
        return token;
    }

    @Override
    @Transactional(readOnly = true)
    public String refreshToken(ChannelUserQO channelUserQO) {
        ChannelUserRegisterPO userPO = channelUserDao.get(channelUserQO);
        Assert.isNotNullThrows(userPO, new BizException(BizEnum.EMPTY));
        return createToken(new ChannelCacheUser(userPO.getId(), userPO.getPhone()));
    }

    /**
     *  获取载体中加密的phone
     *
     *  @param  token
     *  @return String
     */
    public String getPayloadPhone(String token) {
        try {
            String payload = JWT.decode(token).getPayload();
            String json = Base64Utils.decodeToStr(payload);

            Object data = JsonUtils.get(json).get("data");
            Assert.isNotNull(data);

            String phone = ClassConvertUtils.castString(data);
            Assert.isNotEmpty(phone);
            return phone;
        } catch (Exception e) {
            throw new BizException(BizEnum.USER_TOKEN_PARSE_ERROR);
        }
    }

    /**
     *  解密手机号
     *
     *  @param  encryptPhone 密文手机号
     *  @return String
     */
    public String decryptPayloadPhone(String encryptPhone) {
        //  获取缓存
        try {
            AES cbc = AES.createCbc(HexUtils.decode(tokenConfig.getParamKey()), HexUtils.decode(tokenConfig.getParamIv()));
            return cbc.decryptFromHexStr(encryptPhone);
        } catch (Exception e) {
            throw new BizException(BizEnum.USER_TOKEN_DECRYPT_ERROR);
        }
    }
}
